package jwt

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"git.supremind.info/product/box/vehicle/internal/util"
	"strings"
	"time"
)

const (
	HeaderAlg  = "HS256"
	HeaderType = "JWT"
	ClaimIss   = "visionmind"
)

var (
	jwtHeader = Header{
		Alg:  HeaderAlg,
		Type: HeaderType,
	}
	ErrNotAvailable = errors.New("token is expired or not available yet")
	ErrInvalid      = errors.New("invalid token")
)

type Jwt struct {
	Header Header
	Claim  Claim
	Sign   string //JWT签名
}

type Header struct {
	Alg  string `json:"alg"` //签名算法
	Type string `json:"typ"` //固定为JWT
}

type Claim struct {
	Iss string `json:"iss"` //签发者,固定为visionmind
	Exp int64  `json:"exp"` //过期时间
	Nbf int64  `json:"nbf"` //生效起始时间

	NS   string `json:"ns"`   //服务id
	Role string `json:"role"` //权限
	Desc string `json:"desc"` //额外信息
}

func (j *Jwt) GenJwtToken(key []byte) (string, error) {
	headerBytes, _ := json.Marshal(&jwtHeader)
	header := base64.URLEncoding.EncodeToString(headerBytes)

	j.Claim.Iss = ClaimIss
	claimBytes, err := json.Marshal(&j.Claim)
	if err != nil {
		return "", err
	}
	encrypted, err := util.AesEncryptCBC(claimBytes, key)
	if err != nil {
		return "", err
	}
	claim := base64.URLEncoding.EncodeToString(encrypted)

	mac := hmac.New(sha256.New, key)
	mac.Write([]byte(header + "." + claim))
	sign := base64.URLEncoding.EncodeToString(mac.Sum(nil))

	return header + "." + claim + "." + sign, nil
}

func ParseJwt(token string, key []byte) (*Jwt, error) {
	splitedToken := strings.Split(token, ".")
	if len(splitedToken) != 3 {
		return nil, ErrInvalid
	}

	//check signature
	mac := hmac.New(sha256.New, key)
	mac.Write([]byte(splitedToken[0] + "." + splitedToken[1]))
	sign := base64.URLEncoding.EncodeToString(mac.Sum(nil))
	if sign != splitedToken[2] {
		return nil, ErrInvalid
	}

	//check header
	header := Header{}
	headerBytes, err := base64.URLEncoding.DecodeString(splitedToken[0])
	if err != nil {
		return nil, ErrInvalid
	}
	err = json.Unmarshal(headerBytes, &header)
	if err != nil || header.Type != HeaderType || header.Alg != HeaderAlg {
		return nil, ErrInvalid
	}

	//check claim
	claim := Claim{}
	claimBytes, err := base64.URLEncoding.DecodeString(splitedToken[1])
	if err != nil {
		return nil, ErrInvalid
	}
	//decrypt claim
	decrypted, err := util.AesDecryptCBC(claimBytes, key)
	if err != nil {
		return nil, ErrInvalid
	}
	err = json.Unmarshal(decrypted, &claim)
	if err != nil || claim.Iss != ClaimIss {
		return nil, ErrInvalid
	}

	exp := time.Unix(claim.Exp, 0)
	nbf := time.Unix(claim.Nbf, 0)
	now := time.Now()
	if now.Before(nbf) || (claim.Exp != -1 && now.After(exp)) {
		return nil, ErrNotAvailable
	}

	jwt := Jwt{
		Header: header,
		Claim:  claim,
		Sign:   splitedToken[2],
	}
	return &jwt, nil
}
